/*
 * Copyright (c) 2011 Nokia Corporation.
 */

#include <QFile>
#include <QXmlStreamReader>
#include <QVariant>
#include <QPainter>
#include <QEvent>
#include <QDebug>
#include <QAudioOutput>
#include <QThread>

#include <QtDeclarative/QDeclarativeContext>

#include "level.h"

Level::Level() : m_rowCount(0), m_columnCount(0)
{
    /***********************************************************************************************
    *Setting up the Sound player object, this object will live in a separate thread to ensure that *
    *sound playback does not interfere with the game itself.                                       *
    *                                                                                              *
    *Connection type set to Qt::QueuedConnection, this is the connection type required for objects *
    *living in different threads. This would also get handled by the default Qt::AutoConnection,   *
    *but this would make the code less readable.                                                   *
    ***********************************************************************************************/
    m_player = new SoundPlayer;
    connect(this, SIGNAL(bgSoundRequest()), m_player, SLOT(startBGSound()), Qt::QueuedConnection);
    connect(this, SIGNAL(boingSoundRequest()) ,m_player, SLOT(playBoing()), Qt::QueuedConnection);
    connect(this, SIGNAL(groanSoundRequest()) ,m_player, SLOT(playGroan()), Qt::QueuedConnection);
    connect(this, SIGNAL(stopSounds()), m_player, SLOT(stopSounds()), Qt::QueuedConnection);
    connect(m_player, SIGNAL(soundsStopped()), this, SIGNAL(readyToQuit()), Qt::QueuedConnection);
}

Level::~Level()
{
    /***********************************************************************************************
    *Because the SoundPlayer object lives in another thread, it can not  be a child of the Level   *
    *Object and it needs to be deleted by hand.                                                    *
    ***********************************************************************************************/
    delete m_player;
}

/***************************************************************************************************
*All sound playback is handled by an object living in another thread, playback needs to be         *
*invoked by means of a signal and queued connection.                                               *
***************************************************************************************************/
void Level::startBGSound()
{
#ifndef NO_SOUND
    emit bgSoundRequest();
#endif
}

void Level::playBoing()
{
#ifndef NO_SOUND
    emit boingSoundRequest();
#endif
}

void Level::playGroan()
{
#ifndef NO_SOUND
    emit groanSoundRequest();
#endif
}
/**************************************************************************************************/

QString Level::item(int row, int column) const
{
    /***********************************************************************************************
    *Verifying that the row and column are in bounds.                                              *
    *Extracting the string representing the item in that row and column.                           *
    ***********************************************************************************************/
    if ( row < 0 || row >= m_rowCount || column < 0 || column >= m_columnCount )
        return QString();
    const QString & text = m_map.at(row);
    if ( column >= text.count())
        return QString(" ");
    return text.at(column);
}

bool Level::load(QString fileName)
{
    /***********************************************************************************************
    *Clearing the levels old data.                                                                 *
    ***********************************************************************************************/
    m_rowCount = m_columnCount = 0;
    m_map.clear();

    /***********************************************************************************************
    *Opening the level file in read only mode.                                                     *
    ***********************************************************************************************/
    if(fileName.isEmpty() || !QFile::exists(fileName))
        fileName = ":/level/level.xml";
    QFile file(fileName);
    if (!file.open(QFile::ReadOnly | QFile::Text)) {
        qDebug() << Q_FUNC_INFO << file.errorString();
        return false;
    }

    /***********************************************************************************************
    *Using a QXmlStreamReader to parse the level file.                                             *
    ***********************************************************************************************/
    QXmlStreamReader xml(&file);
    if (xml.readNextStartElement()) {
        if (xml.name() == "level") {
            while (xml.readNextStartElement()) {
                if (xml.name() == "layer") {
                    const QString text = xml.readElementText();
                    m_map.append(text);
                    ++m_rowCount;
                    m_columnCount = qMax(m_columnCount, text.count());
                }
            }
        }
    }

    /***********************************************************************************************
    *Checking xml parsing errors                                                                   *
    ***********************************************************************************************/
    if (xml.hasError()) {
        qDebug() << Q_FUNC_INFO << xml.errorString();
        return false;
    }
    return true;

}

bool Level::intersects(QDeclarativeItem *item1, QDeclarativeItem *item2)
{
    /***********************************************************************************************
    *Using QRect to check if the two items intersect.                                              *
    ***********************************************************************************************/
    if ( !(item1&&item2))
        return false;
    const QRect r1 = QRect(item1->x(),item1->y(),item1->width(),item1->height());
    const QRect r2 = QRect(item2->x(),item2->y(),item2->width(),item2->height());
    return r1.intersects(r2);
}

QVariant Level::collidingItems(QDeclarativeItem *item) const
{
    if (!item)
        return QVariant();
    QList<QObject*> dataList;
    /***********************************************************************************************
    *Extracting the list of items that collide with the item.                                      *
    *Casting the item pointer to a EllipseItem pointer,                                            *
    *if successful clearing the ellipse items m_rect vector.                                       *
    ***********************************************************************************************/
    QList<QGraphicsItem *> list = item->collidingItems();
    EllipseItem *el = qobject_cast<EllipseItem*>(item);
    if (el)
        el->m_rect.clear();

    for ( int i=0; i<list.count();++i ) {
        /*******************************************************************************************
        *Ignore the collision if the items happen to be in a parent-child relation.                *
        *Casting the colliding item pointer to a QDeclarativeItem pointer.                         *
        *******************************************************************************************/
        if ( item->isAncestorOf(list.at(i)) || item->parentItem() != list.at(i)->parentItem())
            continue;
        QDeclarativeItem * collidedItem = qobject_cast<QDeclarativeItem*>(list.at(i));
        if (collidedItem) {
            /***************************************************************************************
            *Ignore the collision if the colliding item happens to be the background               *
            *or one of the ui controls.                                                            *
            *If an EllipseItem append the collision rect to the items m_rect vector.               *
            *Append the item pointer to the result list.                                           *
            ***************************************************************************************/
            if ( collidedItem->objectName() == QLatin1String("arrow") ||
                    collidedItem->objectName() == QLatin1String("background") ||
                    collidedItem->objectName() == QLatin1String("quit"))
                continue;
            if (el)
                el->m_rect.append( intersected(item,collidedItem) );
            dataList << collidedItem;
        }
    }
    /***********************************************************************************************
    *Return a QVariant containing the colliding object pointers.                                   *
    ***********************************************************************************************/
    return QVariant::fromValue(dataList);
}

QRect Level::intersected(QDeclarativeItem *item1, QDeclarativeItem *item2) const
{
    if (!(item1&&item2))
        return QRect();
    /***********************************************************************************************
    *Extracting the items shapes.                                                                  *
    *Translate the second items shapes position to be relative to the first item.                  *
    *Calculate the items intersection.                                                             *
    *Return the intersections bounding rectangle.                                                  *
    ***********************************************************************************************/
    QPainterPath p1 = item1->shape();
    QPainterPath p2 = item2->shape().translated(item2->pos()-item1->pos());
    QPainterPath intersection = p2.intersected(p1);

    const QRect bounder = intersection.boundingRect().toRect();
    return bounder;
}

SoundPlayer::SoundPlayer() :m_musicBGOutput(0),m_boingOutput(0),m_groanOutput(0),
    m_musicBGPlaying(false),m_boingPlaying(false),m_groanPlaying(false)
{
    /***********************************************************************************************
    *Creating a dedicated QThread for the SoundPlayer to live in, and moving the object to the     *
    *new thread.                                                                                   *
    ***********************************************************************************************/
    m_soundThread = new QThread;
    this->moveToThread(m_soundThread);
    m_soundThread->start();

    /***********************************************************************************************
    *Setting up the audio format                                                                   *
    ***********************************************************************************************/
    m_format.setSampleRate(22050);
    m_format.setChannelCount(1);
    m_format.setSampleSize(16);
    m_format.setCodec("audio/pcm");
    m_format.setByteOrder(QAudioFormat::LittleEndian);
    m_format.setSampleType(QAudioFormat::SignedInt);

    /***********************************************************************************************
    *Setting a flag indicating if the sound backend supports the given format,                     *
    *and loading the sound files.                                                                  *
    *                                                                                              *
    *The QAudioOutput object creation is differed till the first usage, this is because the Sound  *
    *players constructor is called from the main thread, and constructing them here would prevent  *
    *them form working properly.                                                                   *
    ***********************************************************************************************/
    QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
    if (!info.isFormatSupported(m_format)) {
        m_soundSupport = false;
        qWarning()<<"raw audio format not supported by backend, cannot play audio.";
        return;
    } else {
        m_soundSupport = true;
        m_musicBGFile.setFileName(":/sounds/grieg4f.raw");
        m_boingtFile.setFileName(":/sounds/boink.raw");
        m_groanFile.setFileName(":/sounds/groan.raw");
    }
}

SoundPlayer::~SoundPlayer()
{
    /***********************************************************************************************
    *Calling the sound thread to exit its event loop, waiting for the thread do finish and deleting*
    *the instance.                                                                                 *
    ***********************************************************************************************/
    m_soundThread->exit(0);
    m_soundThread->wait();
    delete m_soundThread;
}

/***************************************************************************************************
*Sound playback slots, called by signals emitted by the Level object.                              *
*The audio output objects get constructed the first time the slot is called.                       *
***************************************************************************************************/
void SoundPlayer::startBGSound()
{
    if(m_musicBGPlaying) return;
    if(!m_soundSupport) return;
    if (m_musicBGOutput) {
        m_musicBGOutput->start(&m_musicBGFile);
        m_musicBGPlaying = true;
    } else{
        if (m_musicBGFile.open(QIODevice::ReadOnly)) {
            m_musicBGOutput = new QAudioOutput(m_format,this);
            connect(m_musicBGOutput,SIGNAL(stateChanged(QAudio::State)),
                    SLOT(outputStateChanged(QAudio::State)));
            m_musicBGOutput->start(&m_musicBGFile);
            m_musicBGPlaying = true;
        }
    }
}

void SoundPlayer::playBoing()
{
    if(!m_soundSupport) return;
    if (m_boingOutput) {
        if(m_boingPlaying) return;
        m_boingOutput->start(&m_boingtFile);
        m_boingPlaying = true;
    } else{
        if (m_boingtFile.open(QIODevice::ReadOnly)) {
            m_boingOutput = new QAudioOutput(m_format,this);
            connect(m_boingOutput,SIGNAL(stateChanged(QAudio::State)),
                    SLOT(outputStateChanged(QAudio::State)));
            m_boingOutput->start(&m_boingtFile);
        }
    }
}

void SoundPlayer::playGroan()
{
    if(!m_soundSupport) return;
    if (m_groanOutput) {
        if(m_groanPlaying) return;
        m_groanOutput->start(&m_groanFile);
        m_groanPlaying = true;
    } else{
        if (m_groanFile.open(QIODevice::ReadOnly)) {
            m_groanOutput = new QAudioOutput(m_format,this);
            connect(m_groanOutput,SIGNAL(stateChanged(QAudio::State)),
                    SLOT(outputStateChanged(QAudio::State)));
            m_groanOutput->start(&m_groanFile);
        }
    }
}
/**************************************************************************************************/

void SoundPlayer::stopSounds()
{
    /***********************************************************************************************
    *Stop all sound playback and emit the signal indicating it is now safe to exit the application *
    ***********************************************************************************************/
    if(m_soundSupport)
    {
        if(m_musicBGOutput)
            m_musicBGOutput->stop();
        if(m_boingOutput)
            m_boingOutput->stop();
        if(m_groanOutput)
            m_groanOutput->stop();
    }
    emit soundsStopped();
}

void SoundPlayer::outputStateChanged(QAudio::State state)
{
    /***********************************************************************************************
    *If signal sender == m_musicBGOutput resume background music playback.                         *
    *If signal sender == m_boingOutput drop the boing playing flag and prepare for next playback.  *
    *If signal sender == m_groanOutput drop the groan playing flag and prepare for next playback.  *
    ***********************************************************************************************/
    if (state == QAudio::IdleState) {
        if (sender() == m_musicBGOutput ) {
            m_musicBGOutput->stop();
            m_musicBGFile.seek(0);
            m_musicBGOutput->start(&m_musicBGFile);
        } else if (sender() == m_boingOutput ) {
            m_boingPlaying = false;
            m_boingOutput->stop();
            m_boingtFile.seek(0);
        } else if (sender() == m_groanOutput ) {
            m_groanPlaying = false;
            m_groanOutput->stop();
            m_groanFile.seek(0);
        }
    }
}

EllipseItem::EllipseItem(QDeclarativeItem *parent)
    : QDeclarativeItem(parent), m_margin(0),m_burn(false)
{
    /***********************************************************************************************
    *Set the items flag to indicate that the item has some contents.                               *
    ***********************************************************************************************/
    setFlag(QGraphicsItem::ItemHasNoContents, false);
}

QPainterPath EllipseItem::shape () const
{
    /***********************************************************************************************
    *Return the items shape as an  ellipse taking in to account the items size and margins.        *
    ***********************************************************************************************/
    QPainterPath path;
    path.addEllipse( m_margin, m_margin,width()-m_margin*2,height()-m_margin*2);
    return path;
}

void EllipseItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
                        QWidget *widget)
{
    Q_UNUSED(option)
    Q_UNUSED(widget)

    /***********************************************************************************************
    *Draw the items shape.                                                                         *
    ***********************************************************************************************/
    painter->save();
    painter->setPen(Qt::red);
    if (m_burn)
        painter->setBrush(Qt::black);
    else
        painter->setBrush(Qt::green);

    painter->drawPath(shape());
    painter->restore();
}

MultiTouchItem::MultiTouchItem(QDeclarativeItem *parent) : QDeclarativeItem(parent)
{
    setAcceptTouchEvents(true);
    setAcceptedMouseButtons(Qt::LeftButton);
    setFlag(QGraphicsItem::ItemHasNoContents, false);
}

void MultiTouchItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
                           QWidget *widget)
{
    Q_UNUSED(option)
    Q_UNUSED(widget)

    /***********************************************************************************************
    *Draw the items pixmap with 0.5 opacity.                                                       *
    ***********************************************************************************************/
    painter->save();
    painter->setOpacity(0.5);
    painter->drawPixmap(0,0,width(),height(), m_pixmap);
    painter->restore();
}

bool MultiTouchItem::sceneEvent(QEvent *event)
{
    /***********************************************************************************************
    *Overloaded items scene event, to handle touch and mouse press events.                         *
    *Handle TouchBegin, TouchUpdate, TouchEnd and mouse press/release events or revert to          *
    *the QDeclarativeItems default implementation.                                                 *
    ***********************************************************************************************/
    switch (event->type()) {
    case QEvent::TouchBegin:
        emit pressed();
        break;

    case QEvent::TouchUpdate:
        break;

    case QEvent::TouchEnd:
        emit released();
        break;

    case QEvent::GraphicsSceneMousePress:
        emit pressed();
        break;

    case QEvent::GraphicsSceneMouseRelease:
        emit released();
        break;

    default:
        return QDeclarativeItem::sceneEvent(event);
    }

    return true;
}

void MultiTouchItem::setSource(const QString &s)
{
    /***********************************************************************************************
    *Set the items pixmap and schedule a redraw.                                                   *
    ***********************************************************************************************/
    m_source = s;
    m_pixmap = QPixmap(s);
    update();
}
